/*****************************************************************************
*                     Organizacion de Datos - 75.06                          *
*                         Catedra Lic. Servetto                              *
*            Facultad de Ingenieria - Universidad de Buenos Aires            *
******************************************************************************/

#include "ParseadorComandos.h"
#include "AdministradorDeOperaciones.h"
#include "AdministradorMetadata.h"

#include <map>
#include <list>
#include <string>
#include <fstream>
#include <sstream>

namespace CapaConsulta 
{

	Operacion* ParseadorComandos::ParsearSeleccion(list<string>* operacion, int numeroOperacion) {

		Seleccion* operacionSeleccion = new Seleccion();
		CondicionCompuesta* condicionPorDefecto = new CondicionCompuestaAND();
		operacionSeleccion->Condiciones = condicionPorDefecto;
		stringstream nombreArchivo;
		nombreArchivo << "Resultado" << numeroOperacion << ".csv";
		operacionSeleccion->NombreArchivoResultado = nombreArchivo.str().c_str();
		map<string, AdministradorMetadata*> administradores = *(new map<string, AdministradorMetadata*>());

		/***************************************/
		/* SELECT                              */
		/***************************************/

		string selectSupuesto = operacion->front();
		if (selectSupuesto != "SELECT")
		{
			cerr << "La seleccion de campos espera el comando SELECT, se encontro " << selectSupuesto << "." << endl;
			throw new exception();
		}
		ParseadorComandos::SacarConComprobacion(operacion, "SELECT");

		/***************************************/
		/* Clase1.Atributo , Clase2.Atributo   */
		/* o                                   */
		/* *                                   */
		/***************************************/

		string nombreAtributoSupuesto = operacion->front();
		ParseadorComandos::SacarConComprobacion(operacion, "SELECT");
		bool tengoAsterisco = false;
		list<ClaseAtributo>* atributosAValidar = new list<ClaseAtributo>();

		if (nombreAtributoSupuesto == "*")
		{
			tengoAsterisco = true;
		}
		else
		{
			while(nombreAtributoSupuesto != "FROM")
			{
				if (!ParseadorComandos::ValidarNombreAtributoConClase(nombreAtributoSupuesto))
				{
					cerr << "No se puede parsear el atributo " << nombreAtributoSupuesto << ". El formato del atributo debe ser 'Clase.Atributo'." << endl;
					throw new exception();
				}

				string atributoACorroborar = ParseadorComandos::BuscarNombreAtributo(nombreAtributoSupuesto);
				string claseACorroborar = ParseadorComandos::BuscarNombreClase(nombreAtributoSupuesto);

				operacionSeleccion->CamposSeleccion->push_back(atributoACorroborar);
				ClaseAtributo campoSeleccionTemporal;
				campoSeleccionTemporal.NombreClase = claseACorroborar;
				campoSeleccionTemporal.NombreAtributo = atributoACorroborar;
				operacionSeleccion->CamposSeleccionConClase->push_back(campoSeleccionTemporal);
				atributosAValidar->push_back(campoSeleccionTemporal);

				string comaOFromSupuesto = operacion->front();

				if (comaOFromSupuesto != "," && comaOFromSupuesto != "FROM")
				{
					cerr << "La seleccion de campos espera los nombres de los atributos separados por comas y luego un FROM, en lugar de una , o un FROM se encontro " << comaOFromSupuesto << "." << endl;
					throw new exception();
				}

				if (comaOFromSupuesto == ",")
				{
					ParseadorComandos::SacarConComprobacion(operacion, "SELECT");
					nombreAtributoSupuesto = operacion->front();
					ParseadorComandos::SacarConComprobacion(operacion, "SELECT");
				}
				else if (comaOFromSupuesto == "FROM")
				{
					nombreAtributoSupuesto = comaOFromSupuesto;
				}

			}
		}

		/***************************************/
		/* FROM                                */
		/***************************************/

		string fromSupuesto = operacion->front();
		if (fromSupuesto != "FROM")
		{
			cerr << "La seleccion de campos espera el comando FROM despues de los atributos, se encontro " << fromSupuesto << "." << endl;
			throw new exception();
		}
		ParseadorComandos::SacarConComprobacion(operacion, "SELECT");

		/***************************************/
		/* Clase1                              */
		/***************************************/

		string claseSupuesto = operacion->front();

		AdministradorMetadata* administrador = new AdministradorMetadata(claseSupuesto);

		if (!administrador->ExisteClase())
		{
			cerr << "No se puede encontrar la clase '" << claseSupuesto << "' en el repositorio." << endl;
			throw new exception();
		}

		operacionSeleccion->ClaseOperacion = administrador->ClaseMetadata;

		operacion->pop_front();

		// Si es un SELECT * selecciono por todos los campos
		if (tengoAsterisco)
		{
			for (list<Atributo>::iterator i = administrador->ClaseMetadata->Miembros->begin(); i != administrador->ClaseMetadata->Miembros->end(); ++i)
			{
				Atributo atributo = *i;

				ClaseAtributo atributoDeSeleccionDeTodos = (*new ClaseAtributo());
				atributoDeSeleccionDeTodos.NombreClase = claseSupuesto;
				atributoDeSeleccionDeTodos.NombreAtributo = atributo.Nombre;
				operacionSeleccion->CamposSeleccionConClase->push_back(atributoDeSeleccionDeTodos);
			}
		}

		administradores.insert( *(new pair<string, AdministradorMetadata*>(claseSupuesto, administrador)) );

		/******************************************************/
		/* JOIN Clase2 ON Clase2.Atributo = Clase1.Atributo   */
		/* JOIN Clase3 ON Clase3.Atributo = Clase1.Atributo   */
		/******************************************************/

		if (!operacion->empty())
		{
			string nombreJoinOWhereSupuesto = operacion->front();

			if (nombreJoinOWhereSupuesto != "JOIN" && nombreJoinOWhereSupuesto != "WHERE" && nombreJoinOWhereSupuesto != "ORDER")
			{
				cerr << "La seleccion de campos espera o JOIN con mas tablas, una condicion WHERE o un ORDER. Se encontro " << nombreJoinOWhereSupuesto << " en ves de JOIN o WHERE." << endl;
				throw new exception();
			}

			string nombreJoinSupuesto = nombreJoinOWhereSupuesto;

			int contadorJoins = 1;

			while(nombreJoinSupuesto == "JOIN")
			{
				/***************************************/
				/* ClaseN                              */
				/***************************************/
				ParseadorComandos::SacarConComprobacion(operacion, "SELECT");
				string claseJoinSupuesto = operacion->front();
				Join objetoJoin;

				if (administradores.find(claseJoinSupuesto) != administradores.end())
				{
					cerr << "No se puede realizar otro JOIN por la clase " << claseJoinSupuesto << ". Ya existe un JOIN por esa clase." << endl;
					throw new exception();
				}

				AdministradorMetadata* administradorParaJoin = new AdministradorMetadata(claseJoinSupuesto);

				administradores.insert( *(new pair<string, AdministradorMetadata*>(claseJoinSupuesto, administradorParaJoin)) );

				if (!administradorParaJoin->ExisteClase())
				{
					cerr << "No se puede encontrar la clase '" << claseJoinSupuesto << "' en el repositorio especificada en el JOIN numero " << contadorJoins << "." << endl;
					throw new exception();
				}

				ParseadorComandos::SacarConComprobacion(operacion, "SELECT");

				// Si es un SELECT * selecciono por todos los campos del JOIN tambien
				if (tengoAsterisco)
				{
					for (list<Atributo>::iterator i = administradorParaJoin->ClaseMetadata->Miembros->begin(); i != administradorParaJoin->ClaseMetadata->Miembros->end(); ++i)
					{
						Atributo atributo = *i;

						ClaseAtributo atributoDeSeleccionDeTodos = (*new ClaseAtributo());
						atributoDeSeleccionDeTodos.NombreClase = claseJoinSupuesto;
						atributoDeSeleccionDeTodos.NombreAtributo = atributo.Nombre;
						operacionSeleccion->CamposSeleccionConClase->push_back(atributoDeSeleccionDeTodos);
					}
				}

				operacionSeleccion->ClaseJoin = administradorParaJoin->ClaseMetadata;

				/***************************************/
				/* ON                                  */
				/***************************************/

				string onSupuesto = operacion->front();
				if (onSupuesto != "ON")
				{
					cerr << "La seleccion de campos espera el comando ON despues de una clase en un JOIN, se encontro " << onSupuesto << "." << endl;
					throw new exception();
				}
				ParseadorComandos::SacarConComprobacion(operacion, "SELECT");

				/***************************************/
				/* ClaseN.Atributo                     */
				/***************************************/

				string atributoConClaseSupuesto = operacion->front();
				if (!ParseadorComandos::ValidarNombreAtributoConClase(atributoConClaseSupuesto))
				{
					cerr << "No se puede parsear el atributo " << atributoConClaseSupuesto << ". El formato del atributo debe ser 'Clase.Atributo'." << endl;
					throw new exception();
				}
				ParseadorComandos::SacarConComprobacion(operacion, "SELECT");

				string soloLaClase = ParseadorComandos::BuscarNombreClase(atributoConClaseSupuesto);
				if (soloLaClase != claseJoinSupuesto)
				{
					cerr << "La seleccion de campos espera como clase del primer campo del JOIN la clase del JOIN, se encontro " << soloLaClase << " y se esperaba " << claseJoinSupuesto << "." << endl;
					throw new exception();
				}

				string soloElAtributo = ParseadorComandos::BuscarNombreAtributo(atributoConClaseSupuesto);
				if (!administradorParaJoin->EsAtributoDeLaClase(soloElAtributo))
				{
					cerr << "El atributo  " << soloElAtributo << " no pertenece a la clase" << claseJoinSupuesto << "." << endl;
					throw new exception();
				}

				operacionSeleccion->CampoJoin = administradorParaJoin->BuscarAtributoPorNombre(soloElAtributo);
				objetoJoin.ClaseJoin.NombreAtributo = soloElAtributo;
				objetoJoin.ClaseJoin.NombreClase = claseJoinSupuesto;

				/***************************************/
				/* =                                   */
				/***************************************/

				string igualSupuesto = operacion->front();
				if (igualSupuesto != "=")
				{
					cerr << "La seleccion de campos espera la unicamente la condicion = entre los campos del JOIN, se encontro " << igualSupuesto << "." << endl;
					throw new exception();
				}
				ParseadorComandos::SacarConComprobacion(operacion, "SELECT");

				/***************************************/
				/* Clase1.Atributo                     */
				/***************************************/

				string atributoConClaseSupuesto2 = operacion->front();

				if (!ParseadorComandos::ValidarNombreAtributoConClase(atributoConClaseSupuesto2))
				{
					cerr << "No se puede parsear el atributo " << atributoConClaseSupuesto2 << ". El formato del atributo debe ser 'Clase.Atributo'." << endl;
					throw new exception();
				}

				string soloLaClase2 = ParseadorComandos::BuscarNombreClase(atributoConClaseSupuesto2);
				if (soloLaClase2 != claseSupuesto)
				{
					cerr << "La seleccion de campos espera como clase del segundo campo del JOIN la clase del FROM, se encontro " << soloLaClase2 << " y se esperaba " << claseSupuesto << "." << endl;
					throw new exception();
				}

				string soloElAtributo2 = ParseadorComandos::BuscarNombreAtributo(atributoConClaseSupuesto2);
				if (!administrador->EsAtributoDeLaClase(soloElAtributo2))
				{
					cerr << "El atributo  " << soloElAtributo2 << " no pertenece a la clase" << claseSupuesto << "." << endl;
					throw new exception();
				}

				operacion->pop_front();
				
				operacionSeleccion->CampoJoinFrom = administrador->BuscarAtributoPorNombre(soloElAtributo2);

				objetoJoin.From.NombreAtributo = soloElAtributo2;
				objetoJoin.From.NombreClase = claseSupuesto;

				operacionSeleccion->Joins->push_back(objetoJoin);

				// Busco el siguiente JOIN, WHERE, ORDER o vacio para terminar el ciclo.
				
				if (operacion->empty())
				{
					nombreJoinSupuesto ="NULL";
				}
				else
				{
					
					string joinOWhereSupuesto = operacion->front();
	
					if (joinOWhereSupuesto != "JOIN" && joinOWhereSupuesto != "WHERE" && joinOWhereSupuesto != "ORDER")
					{
						cerr << "La seleccion de campos espera o JOIN con mas tablas o una condicion WHERE. Se encontro " << joinOWhereSupuesto << " en ves de JOIN, WHERE o ORDER." << endl;
						throw new exception();
					}
	
					nombreJoinSupuesto = operacion->front();
					/*if (joinOWhereSupuesto == "JOIN")
					{
						ParseadorComandos::SacarConComprobacion(operacion, "SELECT");
						
						ParseadorComandos::SacarConComprobacion(operacion, "SELECT");
					}
					else if (joinOWhereSupuesto == "WHERE" || joinOWhereSupuesto == "ORDER")
					{
						nombreJoinSupuesto = joinOWhereSupuesto;
					}*/
				}
				contadorJoins++;
			}
			contadorJoins--;

			// Como ya tengo todas las clases ahora puedo validar los atributos.
			for(list<ClaseAtributo>::iterator i = atributosAValidar->begin(); i != atributosAValidar->end(); ++i)
			{
				ClaseAtributo atributoActual = (*i);

				if (administradores.find(atributoActual.NombreClase) == administradores.end())
				{
					cerr << "El atributo " << atributoActual.NombreAtributo << " informa que es de la clase " << atributoActual.NombreClase << " pero no se encuentra ninguna clase con ese nombre entre las clases de FROM o de los JOINS." << endl;
					throw new exception();
				}

				AdministradorMetadata* administradorActual = (*administradores.find(atributoActual.NombreClase)).second;

				if (!administradorActual->EsAtributoDeLaClase(atributoActual.NombreAtributo))
				{
					cerr << "El atributo  " << atributoActual.NombreAtributo << " no pertenece a la clase" << atributoActual.NombreClase << "." << endl;
					throw new exception();
				}
			}

			/***************************************/
			/* WHERE                               */
			/***************************************/

			
			if (!operacion->empty())
			{
				string posibleWhere = operacion->front();
	
				if (posibleWhere == "WHERE")
				{
					CondicionCompuesta* condicionCompuesta;
					condicionCompuesta = ParseadorComandos::ParsearWhere("SELECT", operacion, administradores, true, true,"ORDER");
					operacionSeleccion->Condiciones = condicionCompuesta;
				}
			}
			if (!operacion->empty())
			{
				/***************************************/
				/* ORDER                               */
				/***************************************/

				string orderSupuesto = operacion->front();
				if (orderSupuesto != "ORDER")
				{
					cerr << "La seleccion de campos espera la unicamente un ORDER como operacion final y se encontro " << orderSupuesto << "." << endl;
					throw new exception();
				}
				ParseadorComandos::SacarConComprobacion(operacion, "SELECT");

				/***************************************/
				/* BY                                  */
				/***************************************/

				string bySupuesto = operacion->front();
				if (bySupuesto != "BY")
				{
					cerr << "La seleccion de campos espera la unicamente un BY despues del ORDER y se encontro " << bySupuesto << "." << endl;
					throw new exception();
				}
				ParseadorComandos::SacarConComprobacion(operacion, "SELECT");

				/***************************************/
				/* Clase.Atributo                     */
				/***************************************/

				string atributoConClaseSupuesto = operacion->front();
				operacion->pop_front();

				if (!ParseadorComandos::ValidarNombreAtributoConClase(atributoConClaseSupuesto))
				{
					cerr << "No se puede parsear el atributo " << atributoConClaseSupuesto << ". El formato del atributo debe ser 'Clase.Atributo'." << endl;
					throw new exception();
				}

				string soloLaClase = ParseadorComandos::BuscarNombreClase(atributoConClaseSupuesto);
				if (administradores.find(soloLaClase) == administradores.end())
				{
					cerr << "La clase del ORDER BY no se encuentra ni en el FROM ni en un JOIN, la clase espeficicada fue " << soloLaClase << "." << endl;
					throw new exception();
				}

				AdministradorMetadata* administradorActual = (*administradores.find(soloLaClase)).second;

				string soloElAtributo = ParseadorComandos::BuscarNombreAtributo(atributoConClaseSupuesto);
				if (!administradorActual->EsAtributoDeLaClase(soloElAtributo))
				{
					cerr << "El atributo  " << soloElAtributo << " no pertenece a la clase" << soloLaClase << "." << endl;
					throw new exception();
				}

				operacionSeleccion->OrdernarPor = administradorActual->BuscarAtributoPorNombre(soloElAtributo);

				stringstream aux;
				aux << (* administradorActual->ClaseMetadata->NombreClase) << "." << operacionSeleccion->OrdernarPor->Nombre;

				operacionSeleccion->OrdernarPor->Nombre = aux.str();		


				/***************************************/
				/* ASC o DESC                          */
				/***************************************/

				operacionSeleccion->OrdenarAscendente = true;

				if (!operacion->empty())
				{
					string ascODescSupuesto = operacion->front();
					if (ascODescSupuesto != "ASC" && ascODescSupuesto != "DESC")
					{
						cerr << "La seleccion de campos espera unicamente ordenes ASC (ascendente) o DESC (descendente) y se encontro " << ascODescSupuesto << "." << endl;
						throw new exception();
					}
					operacion->pop_front();

					if (ascODescSupuesto == "DESC")
						operacionSeleccion->OrdenarAscendente = false;
				}
			}

			if (!operacion->empty())
			{
				cerr << "Se econtraron " << operacion->size() << " cadena(s) no esperadas al final de la operacion SELECT" << endl;
				throw new exception();
			}

		}

		return operacionSeleccion;
	};

}
